Skip to Content

1. 函数组件:React 的基石

在现代 React 中,我们几乎只使用函数组件。它就是一个返回 UI 描述(JSX)的 JavaScript 函数。

一个最简单的组件:

// src/components/Greeting.jsx // 这就是一个 React 组件 function Greeting() { // 它返回了 JSX,看起来像 HTML,但它是 JavaScript return <h1>Hello, React!</h1>; } // 导出这个组件,以便在其他地方使用 export default Greeting;

在另一个组件中使用它:

// src/App.jsx import Greeting from './components/Greeting'; function App() { return ( <div> <p>Welcome to my app.</p> <Greeting /> {/* 像使用 HTML 标签一样使用你的组件 */} </div> ); } export default App;

关键点:

  • 大写字母开头:组件名必须是 Greeting 而不是 greeting。这是 React 区分原生 HTML 标签 (如 div) 和自定义组件的规则。
  • 单一根元素:组件返回的 JSX 必须被一个父元素包裹。如果不想添加多余的 div,可以使用 Fragment:<>...</>

2. 使用 useState 管理组件状态

组件光有静态内容是不够的,它需要有自己的数据(状态),并且在数据变化时更新视图。这就是 useState 的作用,它相当于 Vue 的 ref

useState 是一个 Hook,你可以把它理解为“钩入” React 功能的特殊函数。

一个计数器例子:

import { useState } from 'react'; // 必须从 'react' 导入 function Counter() { // 1. 调用 useState,传入初始值 0 // 2. 它返回一个数组,包含两项: // - count: 当前的状态值 (只读) // - setCount: 更新这个状态的唯一函数 const [count, setCount] = useState(0); function handleClick() { // 错误的做法!不能直接修改状态 // count++; // 正确的做法:调用 setter 函数,传入新的值 setCount(count + 1); } return ( <div> <p>You clicked {count} times</p> <button onClick={handleClick}>Click me</button> </div> ); }

与 Vue 对比的核心差异(非常重要!):

  • 不可变性 (Immutability):在 Vue 中,你可能会写 count.value++。在 React 中,你绝不能直接修改 count。你必须通过 setCount 函数来“请求”一次更新。React 会用你提供的新值替换旧的值,然后重新渲染组件。
  • 更新对象/数组状态:因为不可变性,更新复杂状态时,你需要创建一个新的对象或数组。
const [user, setUser] = useState({ name: 'Alice', age: 30 }); function handleNameChange() { // 错误: setUser({ name: 'Bob' }); // 这会丢失 age 属性 // 正确: 使用展开语法(...)复制旧属性,再覆盖要修改的属性 setUser({ ...user, name: 'Bob' }); }

3. 使用 props 传递数据

这和 Vue 的 props 思想完全一致:父组件向子组件传递数据。

父组件 App.jsx:

import UserProfile from './UserProfile'; function App() { const userInfo = { name: 'Chris', avatarUrl: 'https://i.imgur.com/yXOvdOSs.jpg' }; return ( <div> {/* 像 HTML 属性一样传递 props */} <UserProfile user={userInfo} posts={15} /> </div> ); }

子组件 UserProfile.jsx:

// props 是一个对象,包含了父组件传递的所有属性 // 通常我们用解构赋值直接获取需要的 props function UserProfile({ user, posts }) { return ( <div> <img src={user.avatarUrl} alt={user.name} /> <h2>{user.name}</h2> <p>Posts: {posts}</p> </div> ); }

子组件通知父组件 (状态提升) 在 Vue 中,你会用 $emit。在 React 中,你直接将一个函数作为 prop 传递下去

// 父组件 Parent.jsx function Parent() { const [name, setName] = useState('World'); // 这个函数将被传递给子组件 const handleChildClick = (newName) => { setName(newName); } return ( <div> <h1>Hello, {name}</h1> {/* 将函数作为 prop 传递 */} <Child onUpdateName={handleChildClick} /> </div> ); } // 子组件 Child.jsx function Child({ onUpdateName }) { return ( // 当按钮被点击时,调用从 props 收到的函数 <button onClick={() => onUpdateName('React')}> Change Name to React </button> ); }

这个“状态提升”模式是 React 中实现子到父通信的标准做法。


4. 使用 useEffect 处理副作用

useEffect 是另一个极其重要的 Hook。它用于处理副作用 (Side Effects),即组件渲染之外的任何操作,比如:

  • API 请求
  • 设置定时器或订阅
  • 手动操作 DOM

它完美地整合了 Vue 的 onMounted, onUpdated, onUnmountedwatch

useEffect 的结构: useEffect(callback, [dependencies])

  1. callback: 你要执行的副作用代码。
  2. [dependencies] (依赖数组): 这是 useEffect 的灵魂,它告诉 React 何时重新运行你的副作用代码。

三种主要用法:

  • 模拟 onMounted: 获取数据

    useEffect(() => { // 这个函数只在组件第一次渲染后执行 console.log('Component has mounted!'); fetch('https://api.example.com/data') .then(res => res.json()) .then(data => setData(data)); }, []); // <-- 依赖数组为空,表示不依赖任何 props 或 state,所以只运行一次
  • 模拟 watch: 依赖项变化时执行

    useEffect(() => { // 首次渲染后会执行一次 // 之后,只有当 userId 发生变化时,才会再次执行 console.log(`User ID changed to: ${userId}`); fetch(`https://api.example.com/users/${userId}`) .then(res => res.json()) .then(data => setUserData(data)); }, [userId]); // <-- 依赖数组里有 userId
  • 模拟 onUnmounted: 清理副作用 useEffect 的回调函数可以返回一个清理函数。这个函数会在组件卸载前,或者在下一次 effect 运行前被调用。

    useEffect(() => { const timerId = setInterval(() => { console.log('tick'); }, 1000); // 返回一个清理函数 return () => { console.log('Clearing interval'); clearInterval(timerId); // 组件卸载时清除定时器,防止内存泄漏 }; }, []); // 空依赖数组,所以清理函数只在卸载时运行

5. 条件渲染与列表渲染

这部分充分体现了 React “All in JS” 的哲学。

  • 条件渲染 (替代 v-if): 使用 JavaScript 的原生条件表达式。

    function UserStatus({ isLoggedIn }) { // 1. 使用三元运算符 return isLoggedIn ? <p>Welcome back!</p> : <p>Please log in.</p>; // 2. 使用 && 短路与 (只在条件为真时渲染) return ( <div> <h2>My App</h2> {isLoggedIn && <button>Logout</button>} </div> ); }
  • 列表渲染 (替代 v-for): 使用 JavaScript 的 .map() 方法。

    function TodoList({ todos }) { return ( <ul> {todos.map(todo => ( // 关键:必须为列表中的每一项提供一个独一无二的 `key` prop // 这和 Vue 的 :key 作用完全一样,用于高效的 DOM diff <li key={todo.id}> {todo.text} </li> ))} </ul> ); }

6. 表单处理 (受控组件)

这是和 Vue 的 v-model 差异较大的地方。React 推荐使用受控组件 (Controlled Components),即表单元素的值完全由 React 的 state 控制。

一个受控的输入框:

import { useState } from 'react'; function NameForm() { const [name, setName] = useState(''); const handleSubmit = (event) => { event.preventDefault(); // 阻止表单默认的提交行为 alert(`A name was submitted: ${name}`); }; return ( <form onSubmit={handleSubmit}> <label> Name: {/* 数据流是单向的: 1. state -> input: `value` 属性绑定了 `name` state。 2. input -> state: `onChange` 事件触发 `setName`,用输入框的当前值更新 state。 */} <input type="text" value={name} onChange={e => setName(e.target.value)} /> </label> <button type="submit">Submit</button> </form> ); }

这看起来比 v-model 繁琐,但它的优点是数据流非常明确。任何时候,组件的 state 都是表单数据的“唯一数据源”,这使得调试和管理状态变得更简单。

Last updated on